{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 日期时间格式化和字符串转换\n",
    "\n",
    "## 学习目标\n",
    "- 掌握使用 `strftime()` 将日期时间格式化为字符串\n",
    "- 理解常用格式化代码的含义和使用方法\n",
    "- 学会使用 `pd.to_datetime()` 解析字符串为日期时间\n",
    "- 掌握处理不规则格式和错误数据的方法\n",
    "- 了解本地化和国际化处理\n",
    "- 通过实际案例巩固知识点\n",
    "\n",
    "## 核心知识点\n",
    "1. **格式化输出**：日期时间 → 字符串（strftime）\n",
    "2. **字符串解析**：字符串 → 日期时间（to_datetime）\n",
    "3. **批量处理**：Series 和 DataFrame 的批量格式化\n",
    "4. **错误处理**：处理无效日期和混合格式\n",
    "5. **实际应用**：日志解析、文件命名、数据导出等场景"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from datetime import datetime\n",
    "import locale"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第一部分：日期时间格式化输出（strftime）\n",
    "\n",
    "### 1.1 基本概念\n",
    "\n",
    "`strftime()` 方法用于将日期时间对象转换为指定格式的字符串。\n",
    "\n",
    "**语法**：\n",
    "- `Timestamp.strftime(format)` - 单个日期时间对象\n",
    "- `Series.dt.strftime(format)` - Series 中的日期时间序列\n",
    "\n",
    "**使用场景**：\n",
    "- 数据展示和报告生成\n",
    "- 文件名生成\n",
    "- 日志记录\n",
    "- 数据导出格式化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 基本格式化示例\n",
    "\n",
    "下面展示如何使用 `strftime()` 将日期时间格式化为不同风格的字符串。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始日期时间:\n",
      "0   2023-06-15 14:30:45\n",
      "1   2023-06-16 14:30:45\n",
      "2   2023-06-17 14:30:45\n",
      "3   2023-06-18 14:30:45\n",
      "4   2023-06-19 14:30:45\n",
      "dtype: datetime64[ns]\n",
      "\n",
      "基本格式化示例:\n",
      "ISO格式: 2023-06-15 14:30:45\n",
      "美式格式: 06/15/2023\n",
      "欧式格式: 15/06/2023\n",
      "中文格式: 2023年06月15日\n",
      "完整格式: Thursday, June 15, 2023\n"
     ]
    }
   ],
   "source": [
    "# 创建日期时间序列\n",
    "dates = pd.Series(pd.date_range('2023-06-15 14:30:45', periods=5, freq='D'))\n",
    "\n",
    "print(\"原始日期时间:\")\n",
    "print(dates)\n",
    "print()\n",
    "\n",
    "# 基本格式化 - 展示不同格式风格\n",
    "print(\"基本格式化示例:\")\n",
    "print(f\"ISO格式: {dates.dt.strftime('%Y-%m-%d %H:%M:%S').iloc[0]}\")\n",
    "print(f\"美式格式: {dates.dt.strftime('%m/%d/%Y').iloc[0]}\")\n",
    "print(f\"欧式格式: {dates.dt.strftime('%d/%m/%Y').iloc[0]}\")\n",
    "print(f\"中文格式: {dates.dt.strftime('%Y年%m月%d日').iloc[0]}\")\n",
    "print(f\"完整格式: {dates.dt.strftime('%A, %B %d, %Y').iloc[0]}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 格式化代码详解\n",
    "\n",
    "`strftime()` 使用格式化代码来指定输出格式。以下是常用的格式化代码及其含义：\n",
    "\n",
    "**重要提示**：格式化代码区分大小写，例如 `%Y`（年份）和 `%y`（两位年份）是不同的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "示例日期时间: 2023-06-15 14:30:45\n",
      "\n",
      "格式化代码对照表:\n",
      "------------------------------------------------------------\n",
      "%Y   -> 2023            (四位年份)\n",
      "%y   -> 23              (两位年份)\n",
      "%m   -> 06              (月份(01-12))\n",
      "%B   -> June            (月份全名)\n",
      "%b   -> Jun             (月份缩写)\n",
      "%d   -> 15              (日期(01-31))\n",
      "%H   -> 14              (24小时制小时(00-23))\n",
      "%I   -> 02              (12小时制小时(01-12))\n",
      "%M   -> 30              (分钟(00-59))\n",
      "%S   -> 45              (秒(00-59))\n",
      "%f   -> 000000          (微秒)\n",
      "%A   -> Thursday        (星期全名)\n",
      "%a   -> Thu             (星期缩写)\n",
      "%w   -> 4               (星期数字(0=Sunday))\n",
      "%j   -> 166             (一年中的第几天)\n",
      "%U   -> 24              (一年中的第几周(Sunday开始))\n",
      "%W   -> 24              (一年中的第几周(Monday开始))\n",
      "%p   -> PM              (AM/PM)\n"
     ]
    }
   ],
   "source": [
    "# 创建示例日期时间\n",
    "sample_date = pd.Timestamp('2023-06-15 14:30:45')\n",
    "\n",
    "# 定义常用格式化代码及其说明\n",
    "format_codes = {\n",
    "    '%Y': '四位年份',\n",
    "    '%y': '两位年份',\n",
    "    '%m': '月份(01-12)',\n",
    "    '%B': '月份全名',\n",
    "    '%b': '月份缩写',\n",
    "    '%d': '日期(01-31)',\n",
    "    '%H': '24小时制小时(00-23)',\n",
    "    '%I': '12小时制小时(01-12)',\n",
    "    '%M': '分钟(00-59)',\n",
    "    '%S': '秒(00-59)',\n",
    "    '%f': '微秒',\n",
    "    '%A': '星期全名',\n",
    "    '%a': '星期缩写',\n",
    "    '%w': '星期数字(0=Sunday)',\n",
    "    '%j': '一年中的第几天',\n",
    "    '%U': '一年中的第几周(Sunday开始)',\n",
    "    '%W': '一年中的第几周(Monday开始)',\n",
    "    '%p': 'AM/PM'\n",
    "}\n",
    "\n",
    "print(f\"示例日期时间: {sample_date}\")\n",
    "print(\"\\n格式化代码对照表:\")\n",
    "print(\"-\" * 60)\n",
    "for code, description in format_codes.items():\n",
    "    formatted = sample_date.strftime(code)\n",
    "    print(f\"{code:4} -> {formatted:15} ({description})\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 常用格式化模式\n",
    "\n",
    "在实际工作中，以下格式化模式经常被使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "实用格式化模式示例:\n",
      "------------------------------------------------------------\n",
      "ISO日期           -> 2023-06-15\n",
      "ISO日期时间         -> 2023-06-15 14:30:45\n",
      "欧式日期            -> 15/06/2023\n",
      "美式日期            -> 06/15/2023\n",
      "英文长格式           -> June 15, 2023\n",
      "中文日期            -> 2023年06月15日\n",
      "完整英文格式          -> Thursday, June 15, 2023\n",
      "12小时制时间         -> 2023-06-15 02:30 PM\n",
      "中文日期时间          -> 2023年06月15日 14时30分\n",
      "紧凑日期格式          -> 20230615\n",
      "年-天数格式          -> 2023-166\n"
     ]
    }
   ],
   "source": [
    "# 定义常用格式化模式\n",
    "practical_formats = {\n",
    "    '%Y-%m-%d': 'ISO日期',\n",
    "    '%Y-%m-%d %H:%M:%S': 'ISO日期时间',\n",
    "    '%d/%m/%Y': '欧式日期',\n",
    "    '%m/%d/%Y': '美式日期',\n",
    "    '%B %d, %Y': '英文长格式',\n",
    "    '%Y年%m月%d日': '中文日期',\n",
    "    '%A, %B %d, %Y': '完整英文格式',\n",
    "    '%Y-%m-%d %I:%M %p': '12小时制时间',\n",
    "    '%Y年%m月%d日 %H时%M分': '中文日期时间',\n",
    "    '%Y%m%d': '紧凑日期格式',\n",
    "    '%Y-%j': '年-天数格式'\n",
    "}\n",
    "\n",
    "print(\"实用格式化模式示例:\")\n",
    "print(\"-\" * 60)\n",
    "for pattern, description in practical_formats.items():\n",
    "    formatted = sample_date.strftime(pattern)\n",
    "    print(f\"{description:15} -> {formatted}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第二部分：批量格式化\n",
    "\n",
    "在实际数据处理中，我们经常需要对整个 Series 或 DataFrame 中的日期时间进行批量格式化。\n",
    "\n",
    "### 2.1 DatetimeIndex 的批量格式化\n",
    "\n",
    "`pd.date_range()` 返回的 DatetimeIndex 可以直接使用 `strftime()` 方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "批量格式化结果（每月第一天）:\n",
      "     original    iso_date      month_year   chinese compact    weekday\n",
      "0  2023-01-01  2023-01-01    January 2023  2023年01月  202301     Sunday\n",
      "1  2023-02-01  2023-02-01   February 2023  2023年02月  202302  Wednesday\n",
      "2  2023-03-01  2023-03-01      March 2023  2023年03月  202303  Wednesday\n",
      "3  2023-04-01  2023-04-01      April 2023  2023年04月  202304   Saturday\n",
      "4  2023-05-01  2023-05-01        May 2023  2023年05月  202305     Monday\n",
      "5  2023-06-01  2023-06-01       June 2023  2023年06月  202306   Thursday\n",
      "6  2023-07-01  2023-07-01       July 2023  2023年07月  202307   Saturday\n",
      "7  2023-08-01  2023-08-01     August 2023  2023年08月  202308    Tuesday\n",
      "8  2023-09-01  2023-09-01  September 2023  2023年09月  202309     Friday\n",
      "9  2023-10-01  2023-10-01    October 2023  2023年10月  202310     Sunday\n",
      "10 2023-11-01  2023-11-01   November 2023  2023年11月  202311  Wednesday\n",
      "11 2023-12-01  2023-12-01   December 2023  2023年12月  202312     Friday\n"
     ]
    }
   ],
   "source": [
    "# 创建日期范围（每月第一天）\n",
    "date_range = pd.date_range('2023-01-01', periods=12, freq='MS')\n",
    "\n",
    "# 批量格式化 - 创建包含多种格式的DataFrame\n",
    "formatted_dates = pd.DataFrame({\n",
    "    'original': date_range,\n",
    "    'iso_date': date_range.strftime('%Y-%m-%d'),\n",
    "    'month_year': date_range.strftime('%B %Y'),\n",
    "    'chinese': date_range.strftime('%Y年%m月'),\n",
    "    'compact': date_range.strftime('%Y%m'),\n",
    "    'weekday': date_range.strftime('%A')\n",
    "})\n",
    "\n",
    "print(\"批量格式化结果（每月第一天）:\")\n",
    "print(formatted_dates)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Series 的批量格式化\n",
    "\n",
    "对于 Series 中的日期时间数据，需要使用 `.dt.strftime()` 访问器。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Series批量格式化结果:\n",
      "             datetime   date_only time_only     am_pm        full_format\n",
      "0 2023-06-15 09:00:00  2023-06-15     09:00  09:00 AM  2023年06月15日 09:00\n",
      "1 2023-06-15 12:00:00  2023-06-15     12:00  12:00 PM  2023年06月15日 12:00\n",
      "2 2023-06-15 15:00:00  2023-06-15     15:00  03:00 PM  2023年06月15日 15:00\n",
      "3 2023-06-15 18:00:00  2023-06-15     18:00  06:00 PM  2023年06月15日 18:00\n",
      "4 2023-06-15 21:00:00  2023-06-15     21:00  09:00 PM  2023年06月15日 21:00\n",
      "5 2023-06-16 00:00:00  2023-06-16     00:00  12:00 AM  2023年06月16日 00:00\n",
      "6 2023-06-16 03:00:00  2023-06-16     03:00  03:00 AM  2023年06月16日 03:00\n",
      "7 2023-06-16 06:00:00  2023-06-16     06:00  06:00 AM  2023年06月16日 06:00\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/9b/jb3nnnqn60x3dtlll6mcf0000000gp/T/ipykernel_66119/3848865989.py:2: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.\n",
      "  time_series = pd.Series(pd.date_range('2023-06-15 09:00', periods=8, freq='3H'))\n"
     ]
    }
   ],
   "source": [
    "# 创建时间序列（每3小时一个数据点）\n",
    "time_series = pd.Series(pd.date_range('2023-06-15 09:00', periods=8, freq='3H'))\n",
    "\n",
    "# 对Series进行批量格式化\n",
    "formatted_series = pd.DataFrame({\n",
    "    'datetime': time_series,\n",
    "    'date_only': time_series.dt.strftime('%Y-%m-%d'),\n",
    "    'time_only': time_series.dt.strftime('%H:%M'),\n",
    "    'am_pm': time_series.dt.strftime('%I:%M %p'),\n",
    "    'full_format': time_series.dt.strftime('%Y年%m月%d日 %H:%M')\n",
    "})\n",
    "\n",
    "print(\"Series批量格式化结果:\")\n",
    "print(formatted_series)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第三部分：字符串解析为日期时间（to_datetime）\n",
    "\n",
    "将字符串转换为日期时间对象是数据清洗中的常见任务。Pandas 提供了 `pd.to_datetime()` 函数来处理各种格式的日期字符串。\n",
    "\n",
    "### 3.1 自动解析\n",
    "\n",
    "`pd.to_datetime()` 可以自动识别多种常见的日期格式，无需指定格式字符串。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "自动解析不同格式的日期字符串:\n",
      "------------------------------------------------------------\n",
      "2023-06-15                -> 2023-06-15 00:00:00\n",
      "06/15/2023                -> 2023-06-15 00:00:00\n",
      "15-Jun-2023               -> 2023-06-15 00:00:00\n",
      "2023-06-15 14:30:00       -> 2023-06-15 14:30:00\n",
      "June 15, 2023             -> 2023-06-15 00:00:00\n"
     ]
    }
   ],
   "source": [
    "# 测试自动解析不同格式的日期字符串\n",
    "date_strings = [\n",
    "    '2023-06-15',\n",
    "    '06/15/2023',\n",
    "    '15-Jun-2023',\n",
    "    '2023-06-15 14:30:00',\n",
    "    'June 15, 2023'\n",
    "]\n",
    "\n",
    "print(\"自动解析不同格式的日期字符串:\")\n",
    "print(\"-\" * 60)\n",
    "for date_str in date_strings:\n",
    "    try:\n",
    "        parsed = pd.to_datetime(date_str)\n",
    "        print(f\"{date_str:25} -> {parsed}\")\n",
    "    except Exception as e:\n",
    "        print(f\"{date_str:25} -> 解析失败: {str(e)[:30]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 指定格式解析\n",
    "\n",
    "当日期字符串格式特殊或自动解析失败时，可以使用 `format` 参数指定格式。**指定格式的解析速度更快，也更准确。**\n",
    "\n",
    "**优势**：\n",
    "- 性能更好（不需要尝试多种格式）\n",
    "- 更准确（避免误解析）\n",
    "- 支持特殊格式（如中文日期、紧凑格式等）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "指定格式解析示例:\n",
      "----------------------------------------------------------------------\n",
      "20230615        (格式: %Y%m%d      ) [紧凑日期格式    ] -> 2023-06-15 00:00:00\n",
      "15/06/23        (格式: %d/%m/%y    ) [欧式短日期     ] -> 2023-06-15 00:00:00\n",
      "2023年6月15日      (格式: %Y年%m月%d日   ) [中文日期      ] -> 2023-06-15 00:00:00\n",
      "Jun-15-2023     (格式: %b-%d-%Y    ) [月份缩写格式    ] -> 2023-06-15 00:00:00\n"
     ]
    }
   ],
   "source": [
    "# 指定格式解析示例\n",
    "custom_formats = [\n",
    "    ('20230615', '%Y%m%d', '紧凑日期格式'),\n",
    "    ('15/06/23', '%d/%m/%y', '欧式短日期'),\n",
    "    ('2023年6月15日', '%Y年%m月%d日', '中文日期'),\n",
    "    ('Jun-15-2023', '%b-%d-%Y', '月份缩写格式')\n",
    "]\n",
    "\n",
    "print(\"指定格式解析示例:\")\n",
    "print(\"-\" * 70)\n",
    "for date_str, fmt, desc in custom_formats:\n",
    "    try:\n",
    "        parsed = pd.to_datetime(date_str, format=fmt)\n",
    "        print(f\"{date_str:15} (格式: {fmt:12}) [{desc:10}] -> {parsed}\")\n",
    "    except Exception as e:\n",
    "        print(f\"{date_str:15} (格式: {fmt:12}) -> 解析失败: {str(e)[:30]}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第四部分：处理不规则格式和错误数据\n",
    "\n",
    "实际数据中经常包含无效日期、空值或混合格式。我们需要学会如何处理这些异常情况。\n",
    "\n",
    "### 4.1 使用 errors 参数\n",
    "\n",
    "`pd.to_datetime()` 的 `errors` 参数可以控制如何处理无法解析的日期字符串：\n",
    "\n",
    "- `errors='raise'`（默认）：遇到错误时抛出异常\n",
    "- `errors='coerce'`：将无法解析的值转换为 `NaT`（Not a Time）\n",
    "- `errors='ignore'`：保持原值不变，返回原始输入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "处理混合格式数据:\n",
      "============================================================\n",
      "\n",
      "方法1: errors='coerce' (错误值变为NaT):\n",
      "------------------------------------------------------------\n",
      "✓ 2023-06-15      -> 2023-06-15 00:00:00\n",
      "✗ 06/15/2023      -> NaT\n",
      "✗ invalid_date    -> NaT\n",
      "✗ 2023-02-30      -> NaT\n",
      "✗                 -> NaT\n",
      "✓ 2023-12-25      -> 2023-12-25 00:00:00\n",
      "\n",
      "方法2: errors='ignore' (保持原值):\n",
      "------------------------------------------------------------\n",
      "  2023-06-15      -> 2023-06-15 (类型: str)\n",
      "  06/15/2023      -> 06/15/2023 (类型: str)\n",
      "  invalid_date    -> invalid_date (类型: str)\n",
      "  2023-02-30      -> 2023-02-30 (类型: str)\n",
      "                  ->  (类型: str)\n",
      "  2023-12-25      -> 2023-12-25 (类型: str)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/9b/jb3nnnqn60x3dtlll6mcf0000000gp/T/ipykernel_66119/277714154.py:23: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_datetime without passing `errors` and catch exceptions explicitly instead\n",
      "  ignored = pd.to_datetime(mixed_dates, errors='ignore')\n"
     ]
    }
   ],
   "source": [
    "# 模拟包含无效数据的日期字符串列表\n",
    "mixed_dates = [\n",
    "    '2023-06-15',      # 有效日期\n",
    "    '06/15/2023',      # 有效日期\n",
    "    'invalid_date',    # 无效字符串\n",
    "    '2023-02-30',      # 无效日期（2月没有30日）\n",
    "    '',                # 空字符串\n",
    "    '2023-12-25'       # 有效日期\n",
    "]\n",
    "\n",
    "print(\"处理混合格式数据:\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "# 方法1: errors='coerce' - 将错误值转换为NaT\n",
    "coerced = pd.to_datetime(mixed_dates, errors='coerce')\n",
    "print(\"\\n方法1: errors='coerce' (错误值变为NaT):\")\n",
    "print(\"-\" * 60)\n",
    "for orig, parsed in zip(mixed_dates, coerced):\n",
    "    status = \"✓\" if pd.notna(parsed) else \"✗\"\n",
    "    print(f\"{status} {orig:15} -> {parsed}\")\n",
    "\n",
    "# 方法2: errors='ignore' - 保持原值不变\n",
    "ignored = pd.to_datetime(mixed_dates, errors='ignore')\n",
    "print(\"\\n方法2: errors='ignore' (保持原值):\")\n",
    "print(\"-\" * 60)\n",
    "for orig, parsed in zip(mixed_dates, ignored):\n",
    "    print(f\"  {orig:15} -> {parsed} (类型: {type(parsed).__name__})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 自定义解析函数\n",
    "\n",
    "当数据包含多种格式时，可以编写自定义解析函数，依次尝试多种格式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "自定义解析结果:\n",
      "------------------------------------------------------------\n",
      "✓ 2023-06-15      -> 2023-06-15 00:00:00\n",
      "✓ 06/15/2023      -> 2023-06-15 00:00:00\n",
      "✓ 15-Jun-2023     -> 2023-06-15 00:00:00\n",
      "✓ 2023年6月15日      -> 2023-06-15 00:00:00\n",
      "✗ invalid         -> NaT\n"
     ]
    }
   ],
   "source": [
    "def custom_date_parser(date_str):\n",
    "    \"\"\"\n",
    "    自定义日期解析函数，尝试多种格式\n",
    "    \n",
    "    参数:\n",
    "        date_str: 日期字符串\n",
    "        \n",
    "    返回:\n",
    "        Timestamp 或 NaT\n",
    "    \"\"\"\n",
    "    # 处理空值\n",
    "    if pd.isna(date_str) or date_str == '':\n",
    "        return pd.NaT\n",
    "    \n",
    "    # 按优先级尝试多种格式\n",
    "    formats_to_try = [\n",
    "        '%Y-%m-%d',           # ISO格式\n",
    "        '%m/%d/%Y',           # 美式格式\n",
    "        '%d/%m/%Y',           # 欧式格式\n",
    "        '%d-%b-%Y',           # 日期-月份缩写-年份\n",
    "        '%Y年%m月%d日',       # 中文格式\n",
    "        '%Y%m%d',             # 紧凑格式\n",
    "        '%B %d, %Y'           # 英文长格式\n",
    "    ]\n",
    "    \n",
    "    # 依次尝试每种格式\n",
    "    for fmt in formats_to_try:\n",
    "        try:\n",
    "            return pd.to_datetime(date_str, format=fmt)\n",
    "        except:\n",
    "            continue\n",
    "    \n",
    "    # 如果所有格式都失败，尝试自动解析\n",
    "    try:\n",
    "        return pd.to_datetime(date_str)\n",
    "    except:\n",
    "        return pd.NaT\n",
    "\n",
    "# 应用自定义解析函数\n",
    "test_dates = ['2023-06-15', '06/15/2023', '15-Jun-2023', '2023年6月15日', 'invalid']\n",
    "custom_parsed = [custom_date_parser(date) for date in test_dates]\n",
    "\n",
    "print(\"自定义解析结果:\")\n",
    "print(\"-\" * 60)\n",
    "for orig, parsed in zip(test_dates, custom_parsed):\n",
    "    status = \"✓\" if pd.notna(parsed) else \"✗\"\n",
    "    print(f\"{status} {orig:15} -> {parsed}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第五部分：本地化和国际化\n",
    "\n",
    "不同地区使用不同的日期格式和语言。Pandas 支持通过本地化设置来格式化日期时间。\n",
    "\n",
    "**注意**：本地化功能依赖于系统的 locale 设置，在某些环境下可能不可用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "不同语言的月份名称:\n",
      "============================================================\n",
      "\n",
      "英文格式:\n",
      "   1. January 2023\n",
      "   2. February 2023\n",
      "   3. March 2023\n",
      "   4. April 2023\n",
      "   5. May 2023\n",
      "   6. June 2023\n",
      "   7. July 2023\n",
      "   8. August 2023\n",
      "   9. September 2023\n",
      "  10. October 2023\n",
      "  11. November 2023\n",
      "  12. December 2023\n",
      "\n",
      "中文格式:\n",
      "   1. 一月 2023\n",
      "   2. 二月 2023\n",
      "   3. 三月 2023\n",
      "   4. 四月 2023\n",
      "   5. 五月 2023\n",
      "   6. 六月 2023\n",
      "   7. 七月 2023\n",
      "   8. 八月 2023\n",
      "   9. 九月 2023\n",
      "  10. 十月 2023\n",
      "  11. 十一月 2023\n",
      "  12. 十二月 2023\n"
     ]
    }
   ],
   "source": [
    "# 创建示例日期（每月第一天）\n",
    "sample_dates = pd.Series(pd.date_range('2023-01-01', periods=12, freq='MS'))\n",
    "\n",
    "print(\"不同语言的月份名称:\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "# 英文月份名称（默认）\n",
    "print(\"\\n英文格式:\")\n",
    "english_months = sample_dates.dt.strftime('%B %Y')\n",
    "for i, month in enumerate(english_months, 1):\n",
    "    print(f\"  {i:2d}. {month}\")\n",
    "\n",
    "# 尝试设置中文本地化\n",
    "print(\"\\n中文格式:\")\n",
    "try:\n",
    "    locale.setlocale(locale.LC_TIME, 'zh_CN.UTF-8')\n",
    "    chinese_months = sample_dates.dt.strftime('%B %Y')\n",
    "    for i, month in enumerate(chinese_months, 1):\n",
    "        print(f\"  {i:2d}. {month}\")\n",
    "except Exception as e:\n",
    "    print(f\"  中文本地化不可用 ({str(e)[:50]})\")\n",
    "    print(\"  使用自定义映射:\")\n",
    "    \n",
    "    # 自定义中文月份映射\n",
    "    chinese_month_map = {\n",
    "        1: '一月', 2: '二月', 3: '三月', 4: '四月',\n",
    "        5: '五月', 6: '六月', 7: '七月', 8: '八月', \n",
    "        9: '九月', 10: '十月', 11: '十一月', 12: '十二月'\n",
    "    }\n",
    "    \n",
    "    chinese_custom = sample_dates.dt.month.map(chinese_month_map) + ' ' + sample_dates.dt.year.astype(str) + '年'\n",
    "    for i, month in enumerate(chinese_custom, 1):\n",
    "        print(f\"  {i:2d}. {month}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第六部分：实际应用案例\n",
    "\n",
    "下面通过几个实际场景来巩固所学知识。\n",
    "\n",
    "### 6.1 日志文件时间戳解析\n",
    "\n",
    "从日志文件中提取和格式化时间戳是常见的数据处理任务。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "解析后的日志数据:\n",
      "            timestamp    level                     message\n",
      "0 2023-06-15 09:30:15     INFO       User login successful\n",
      "1 2023-06-15 09:31:22    ERROR  Database connection failed\n",
      "2 2023-06-15 09:32:45  WARNING  High memory usage detected\n",
      "3 2023-06-15 09:35:10     INFO                 User logout\n",
      "4 2023-06-15 10:15:33    ERROR                 API timeout\n",
      "\n",
      "格式化后的日志数据:\n",
      "         date      time weekday    level                     message\n",
      "0  2023-06-15  09:30:15     星期四     INFO       User login successful\n",
      "1  2023-06-15  09:31:22     星期四    ERROR  Database connection failed\n",
      "2  2023-06-15  09:32:45     星期四  WARNING  High memory usage detected\n",
      "3  2023-06-15  09:35:10     星期四     INFO                 User logout\n",
      "4  2023-06-15  10:15:33     星期四    ERROR                 API timeout\n"
     ]
    }
   ],
   "source": [
    "# 模拟日志数据\n",
    "log_data = [\n",
    "    '2023-06-15 09:30:15 INFO: User login successful',\n",
    "    '2023-06-15 09:31:22 ERROR: Database connection failed',\n",
    "    '2023-06-15 09:32:45 WARNING: High memory usage detected',\n",
    "    '2023-06-15 09:35:10 INFO: User logout',\n",
    "    '2023-06-15 10:15:33 ERROR: API timeout'\n",
    "]\n",
    "\n",
    "import re\n",
    "\n",
    "def parse_log_entry(log_entry):\n",
    "    \"\"\"\n",
    "    解析日志条目，提取时间戳、级别和消息\n",
    "    \"\"\"\n",
    "    # 使用正则表达式提取时间戳\n",
    "    timestamp_match = re.match(r'(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})', log_entry)\n",
    "    if timestamp_match:\n",
    "        timestamp_str = timestamp_match.group(1)\n",
    "        # 解析时间戳\n",
    "        timestamp = pd.to_datetime(timestamp_str, format='%Y-%m-%d %H:%M:%S')\n",
    "        \n",
    "        # 提取日志级别和消息\n",
    "        remaining = log_entry[len(timestamp_str):].strip()\n",
    "        level_match = re.match(r'(\\w+):', remaining)\n",
    "        level = level_match.group(1) if level_match else 'UNKNOWN'\n",
    "        message = remaining[len(level)+1:].strip() if level_match else remaining\n",
    "        \n",
    "        return {\n",
    "            'timestamp': timestamp,\n",
    "            'level': level,\n",
    "            'message': message\n",
    "        }\n",
    "    return None\n",
    "\n",
    "# 处理日志数据\n",
    "parsed_logs = [parse_log_entry(log) for log in log_data]\n",
    "log_df = pd.DataFrame([log for log in parsed_logs if log is not None])\n",
    "\n",
    "print(\"解析后的日志数据:\")\n",
    "print(log_df)\n",
    "print()\n",
    "\n",
    "# 格式化输出 - 添加便于阅读的日期和时间列\n",
    "log_df['date'] = log_df['timestamp'].dt.strftime('%Y-%m-%d')\n",
    "log_df['time'] = log_df['timestamp'].dt.strftime('%H:%M:%S')\n",
    "log_df['weekday'] = log_df['timestamp'].dt.strftime('%A')\n",
    "\n",
    "print(\"格式化后的日志数据:\")\n",
    "print(log_df[['date', 'time', 'weekday', 'level', 'message']])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2 基于时间戳的文件名生成\n",
    "\n",
    "在数据备份、日志记录等场景中，经常需要根据时间戳生成文件名。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成的文件名示例:\n",
      "------------------------------------------------------------\n",
      "  数据备份     -> backup_20251117_092723.csv\n",
      "  日志文件     -> log_20251117_092723.log\n",
      "  报告文件     -> report_20251117_092723.xlsx\n",
      "  配置文件     -> config_20251117_092723.json\n",
      "\n",
      "批量生成文件名（每日报告）:\n",
      "------------------------------------------------------------\n",
      "  1. daily_report_20230101_000000.csv\n",
      "  2. daily_report_20230102_000000.csv\n",
      "  3. daily_report_20230103_000000.csv\n",
      "  4. daily_report_20230104_000000.csv\n",
      "  5. daily_report_20230105_000000.csv\n"
     ]
    }
   ],
   "source": [
    "def generate_filename(base_name, timestamp, file_type='csv'):\n",
    "    \"\"\"\n",
    "    根据时间戳生成文件名\n",
    "    \n",
    "    参数:\n",
    "        base_name: 文件名基础部分\n",
    "        timestamp: 时间戳\n",
    "        file_type: 文件扩展名\n",
    "        \n",
    "    返回:\n",
    "        格式化的文件名\n",
    "    \"\"\"\n",
    "    time_str = timestamp.strftime('%Y%m%d_%H%M%S')\n",
    "    return f\"{base_name}_{time_str}.{file_type}\"\n",
    "\n",
    "# 生成不同类型的文件名\n",
    "current_time = pd.Timestamp.now()\n",
    "filenames = {\n",
    "    '数据备份': generate_filename('backup', current_time),\n",
    "    '日志文件': generate_filename('log', current_time, 'log'),\n",
    "    '报告文件': generate_filename('report', current_time, 'xlsx'),\n",
    "    '配置文件': generate_filename('config', current_time, 'json')\n",
    "}\n",
    "\n",
    "print(\"生成的文件名示例:\")\n",
    "print(\"-\" * 60)\n",
    "for desc, filename in filenames.items():\n",
    "    print(f\"  {desc:8} -> {filename}\")\n",
    "\n",
    "# 批量生成文件名示例\n",
    "print(\"\\n批量生成文件名（每日报告）:\")\n",
    "print(\"-\" * 60)\n",
    "dates = pd.date_range('2023-01-01', periods=5, freq='D')\n",
    "batch_filenames = [generate_filename('daily_report', date) for date in dates]\n",
    "for i, filename in enumerate(batch_filenames, 1):\n",
    "    print(f\"  {i}. {filename}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.3 数据导出格式化\n",
    "\n",
    "在生成报告或导出数据时，通常需要将日期时间格式化为更易读的形式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "格式化的销售数据（前10条）:\n",
      "================================================================================\n",
      "            日期   星期        月份       季度  sales region\n",
      "0  2023年01月01日  星期日  2023年01月  2023年Qq   4174     上海\n",
      "1  2023年01月02日  星期一  2023年01月  2023年Qq   4507     上海\n",
      "2  2023年01月03日  星期二  2023年01月  2023年Qq   1860     上海\n",
      "3  2023年01月04日  星期三  2023年01月  2023年Qq   2294     上海\n",
      "4  2023年01月05日  星期四  2023年01月  2023年Qq   2130     上海\n",
      "5  2023年01月06日  星期五  2023年01月  2023年Qq   2095     上海\n",
      "6  2023年01月07日  星期六  2023年01月  2023年Qq   4772     广州\n",
      "7  2023年01月08日  星期日  2023年01月  2023年Qq   4092     广州\n",
      "8  2023年01月09日  星期一  2023年01月  2023年Qq   2638     上海\n",
      "9  2023年01月10日  星期二  2023年01月  2023年Qq   3169     广州\n",
      "\n",
      "月度汇总统计:\n",
      "============================================================\n",
      "           总销售额    平均销售额  销售天数\n",
      "月份                            \n",
      "2023年01月  94786  3057.61    31\n",
      "2023年02月  85529  3054.61    28\n",
      "2023年03月  98223  3168.48    31\n",
      "2023年04月  34694  3469.40    10\n"
     ]
    }
   ],
   "source": [
    "# 创建示例销售数据\n",
    "np.random.seed(42)\n",
    "sales_dates = pd.date_range('2023-01-01', periods=100, freq='D')\n",
    "sales_data = pd.DataFrame({\n",
    "    'date': sales_dates,\n",
    "    'sales': np.random.randint(1000, 5000, 100),\n",
    "    'region': np.random.choice(['北京', '上海', '广州'], 100)\n",
    "})\n",
    "\n",
    "# 添加格式化的日期列用于展示和分组\n",
    "sales_data['日期'] = sales_data['date'].dt.strftime('%Y年%m月%d日')\n",
    "sales_data['星期'] = sales_data['date'].dt.strftime('%A')\n",
    "sales_data['月份'] = sales_data['date'].dt.strftime('%Y年%m月')\n",
    "sales_data['季度'] = sales_data['date'].dt.strftime('%Y年Q%q')\n",
    "\n",
    "print(\"格式化的销售数据（前10条）:\")\n",
    "print(\"=\" * 80)\n",
    "print(sales_data[['日期', '星期', '月份', '季度', 'sales', 'region']].head(10))\n",
    "\n",
    "# 按月份汇总统计\n",
    "print(\"\\n月度汇总统计:\")\n",
    "print(\"=\" * 60)\n",
    "monthly_summary = sales_data.groupby('月份')['sales'].agg(['sum', 'mean', 'count']).round(2)\n",
    "monthly_summary.columns = ['总销售额', '平均销售额', '销售天数']\n",
    "print(monthly_summary.head())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结\n",
    "\n",
    "### 核心要点回顾\n",
    "\n",
    "1. **格式化输出（日期时间 → 字符串）**\n",
    "   - 使用 `strftime()` 方法\n",
    "   - 单个对象：`timestamp.strftime(format)`\n",
    "   - Series：`series.dt.strftime(format)`\n",
    "   - DatetimeIndex：`date_range.strftime(format)`\n",
    "\n",
    "2. **字符串解析（字符串 → 日期时间）**\n",
    "   - 使用 `pd.to_datetime()` 函数\n",
    "   - 自动解析：`pd.to_datetime(date_str)`\n",
    "   - 指定格式：`pd.to_datetime(date_str, format='%Y-%m-%d')`\n",
    "   - 错误处理：`errors='coerce'` 或 `errors='ignore'`\n",
    "\n",
    "3. **常用格式化代码**\n",
    "   - `%Y`：四位年份，`%y`：两位年份\n",
    "   - `%m`：月份（01-12），`%B`：月份全名，`%b`：月份缩写\n",
    "   - `%d`：日期（01-31）\n",
    "   - `%H`：24小时制，`%I`：12小时制，`%p`：AM/PM\n",
    "   - `%A`：星期全名，`%a`：星期缩写\n",
    "\n",
    "4. **最佳实践**\n",
    "   - 指定格式解析比自动解析更快、更准确\n",
    "   - 处理混合格式数据时使用 `errors='coerce'`\n",
    "   - 批量处理时优先使用向量化操作\n",
    "   - 根据实际需求选择合适的格式化模式\n",
    "\n",
    "### 常见问题\n",
    "\n",
    "**Q: 为什么指定格式解析更快？**  \n",
    "A: 自动解析需要尝试多种格式，而指定格式直接按格式解析，减少了尝试次数。\n",
    "\n",
    "**Q: 如何处理中文日期格式？**  \n",
    "A: 使用 `format='%Y年%m月%d日'` 指定格式，或使用自定义映射函数。\n",
    "\n",
    "**Q: errors='coerce' 和 errors='ignore' 的区别？**  \n",
    "A: `coerce` 将无效值转为 `NaT`，`ignore` 保持原值不变。\n",
    "\n",
    "### 扩展学习\n",
    "\n",
    "- 学习更多格式化代码：参考 Python `datetime.strftime()` 文档\n",
    "- 时区处理：使用 `tz_localize()` 和 `tz_convert()`\n",
    "- 日期时间运算：时间差计算、日期偏移等\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "ml311",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
